package tacticalLayer;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.jdom.Element;

import planninglanguage.plans.AttackBOPlan;
import planninglanguage.plans.BasicOperatorPlan;
import planninglanguage.plans.BuildBOPlan;
import tacticalLayer.tactics.AttackTactic;
import tacticalLayer.tactics.BuildTactic;
import tacticalLayer.tactics.DefaultTactic;
import tacticalLayer.tactics.EmptyTactic;
import tacticalLayer.tactics.IdleTactic;
import tacticalLayer.tactics.Tactic;
import util.XMLWriter;
import base.ProxyBot;
import base.WargusGameState;
import base.WargusStateImporter;
import base.WargusUnit;

public class ScriptedTacticalLayer extends TacticalLayer {
	public static final int DEBUG_LEVEL = 0;	// 0: silent, 1: some messages, 2: verbose
	
	ProxyBot m_pb;
	HashMap<Integer,Tactic> m_unitTactics = null; // save this
	//- This involves saving which "Tactic" is associated with each unit,
	//  and save ths status of the tactics.
	//- Each Tactic class (in the package tacticalLayer.tactics) should have 
	//  functions to save its state to an XML format and to restore it. 
	//  (Rushab is working on the tactical layer, so we should coordinate 
	//   with him, so that he can implement this) 
	public static PrintStream m_logger = null;
	
	public ScriptedTacticalLayer(ProxyBot pb) {
		if (DEBUG_LEVEL>=1) System.out.println("ScriptedTacticalLayer constructor started");
		m_pb = pb;
		m_unitTactics = new HashMap<Integer,Tactic>();
		m_playerID = 0;
		
		try {
		      m_logger = new PrintStream( new FileOutputStream(new File("log/TACTICAL_log.txt")) );
		} catch (FileNotFoundException e) {
			m_logger = null;
			e.printStackTrace();
		}	
		
		if (DEBUG_LEVEL>=1) System.out.println("ScriptedTacticalLayer constructor finished");
	}
		
	public void run() {
		if (DEBUG_LEVEL>=1) System.out.println("ScriptedTacticalLayer started");
	}
	
	
	public void command(BasicOperatorPlan bo) {
		Tactic t = null;
		boolean run_first_cycle = false;

		// Check the operator to see which Tactic has to be assigned with the unit:

		if (bo instanceof AttackBOPlan) t = new AttackTactic(bo.getUnitID(),(AttackBOPlan)bo);
		if (bo instanceof BuildBOPlan) t = new BuildTactic(bo.getUnitID(),m_playerID,(BuildBOPlan)bo);
		if (t == null) t = new DefaultTactic(bo.getUnitID(),bo);
			
		// If the same goal was given before, do not change the tactic (to prevent the Tactics from resetting):
		{
			Tactic old_t = m_unitTactics.get(bo.getUnitID());	
			if (old_t==null) {
				m_unitTactics.put(bo.getUnitID(), t);	
				run_first_cycle = true;
			} else {
				BasicOperatorPlan old_g = old_t.getGoal();
				BasicOperatorPlan g = t.getGoal();
				
				if (old_g != null && g != null && old_t.getGoal().sameGoal(t.getGoal())) {
					if (DEBUG_LEVEL>=1) System.out.println("ScriptedTacticalLayer: same goal than before for unit " + bo.getUnitID() + ", keeping old Tactic");
				} else {
					if (DEBUG_LEVEL>=1) System.out.println("ScriptedTacticalLayer: different goal than before for unit " + bo.getUnitID() + ", substituting Tactic");
					m_unitTactics.put(bo.getUnitID(), t);
					run_first_cycle = true;
				}
			}
		}
		
		// Execute one cycle of each Tactic
		if (run_first_cycle) {
			LinkedList<Integer> todelete = new LinkedList<Integer>();
			Integer unitID = bo.getUnitID();
			
			Tactic t_old = m_unitTactics.get(unitID);
			Tactic t2 = t_old.cycle(m_currentGameState);
			
			if (t2!=t) {
				if (t2==null) {
					todelete.add(unitID);
				} else {
					m_unitTactics.put(unitID,t2);
				}
			}

			while(todelete.size()>0) {
				Integer i = todelete.remove();
				m_unitTactics.remove(i);
			}			
		}		
	}	
	
	
	public void cycle() throws InterruptedException {
		List<Integer> m_existingTroops = new LinkedList<Integer>();
		
		if (DEBUG_LEVEL>=1) System.out.println("ScriptedTacticalLayer: cycle starts " + m_pb.getCurrCycle());
		m_logger.println("ScriptedTacticalLayer: cycle starts " + m_pb.getCurrCycle());
		if (DEBUG_LEVEL>=1) System.out.println("ScriptedTacticalLayer: get state...");
		m_pb.lockState();
		m_currentGameState = WargusStateImporter.getGameState(m_pb);
		m_pb.unlockState();
		if (DEBUG_LEVEL>=1) System.out.println("ScriptedTacticalLayer: OK");
		
		// Check for any new troops and assign the idle Tactic:
		if (m_currentGameState!=null) {

			
			List<WargusUnit> units = m_currentGameState.getSelfUnits();
			for(WargusUnit u:units) {
				int unitID = u.getUnitID();				
				String unitType = WargusStateImporter.unitTypeToString(u.getType());
				
				m_existingTroops.add(new Integer(unitID));
				
				if (!m_unitTactics.containsKey(unitID)) {
					if (DEBUG_LEVEL>=1) System.out.println("ScriptedTacticalLayer: Unit without tactic: " + unitID + ", assigning default tactic...");
					m_logger.println("ScriptedTacticalLayer: Unit without tactic: " + unitID + ", assigning default tactic...");
					
					if (WargusStateImporter.canMove(unitType)) {
						m_unitTactics.put(unitID, new IdleTactic(unitID));
					} else {
						m_unitTactics.put(unitID, new EmptyTactic(unitID));
					}
				}
			}
		}
		
		// Check if any troop does not exist any more and remove their Tactics
		{
			LinkedList<Integer> todelete = new LinkedList<Integer>();			
			Iterator it = m_unitTactics.keySet().iterator();
			while(it.hasNext()) {
				Integer unitID = (Integer) it.next();
				
				if (!m_existingTroops.contains(unitID)) todelete.add(unitID);
			}			
			while(todelete.size()>0) {
				Integer i = todelete.remove();
				m_unitTactics.remove(i);
			}
			
			
		}
		
		// Execute one cycle of each Tactic
		{
			LinkedList<Integer> todelete = new LinkedList<Integer>();			
			Iterator it = m_unitTactics.keySet().iterator();
			while(it.hasNext()) {
				Integer unitID = (Integer) it.next();
				
				Tactic t = m_unitTactics.get(unitID);
				Tactic t2 = t.cycle(m_currentGameState);
				
				if (t2!=t) {
					if (t2==null) {
						todelete.add(unitID);
					} else {
						m_unitTactics.put(unitID,t2);
					}
				}
			}	
			while(todelete.size()>0) {
				Integer i = todelete.remove();
				m_unitTactics.remove(i);
			}			
		}
		
		// Print the status:
		{
			if (DEBUG_LEVEL>=2) System.out.println("ScriptedTacticalLayer: assigned tactics to units:");
			m_logger.println("ScriptedTacticalLayer: assigned tactics to units:");
			Iterator it = m_unitTactics.keySet().iterator();
			while(it.hasNext()) {
				Integer unitID = (Integer) it.next();
				
				if (DEBUG_LEVEL>=2) System.out.println("- Unit " + unitID + ": " + m_unitTactics.get(unitID).getClass().getSimpleName());
				m_logger.println("- Unit " + unitID + ": " + m_unitTactics.get(unitID).getClass().getSimpleName());
			}
		}

		if (DEBUG_LEVEL>=1) System.out.println("ScriptedTacticalLayer: cycle ends");		
	}
	
	public WargusGameState getCurrentGameState() {
		return m_currentGameState;
	}
	
	public void writeXML(XMLWriter w) {
		w.tag("tactical-layer");
		{
			// XXX assuming Integer in the HashMap is the unitID already stored in each tactic
			// 		as an attribute or a tag..
			for (Tactic t : m_unitTactics.values()) {
				t.writeXML(w);
			}
		}
		w.tag("/tactical-layer");
	}
	public void loadFromXML(Element e) {
		assert e.getName().equals("tactical-layer") :
			"ScriptedTacticalLayer.loadFromXML: Invalid XML Element " +
			e.getName();
		
		for (Object o : e.getChildren("tactic")) {
			Tactic t = Tactic.loadFromXML((Element) o);
			m_unitTactics.put(t.getUnitID(), t);
		}
	}
}
